#ifndef BEZIER_H
#define BEZIER_H

#include <cassert>
#include <algorithm>
#include <vector>

#include "Curves/Interfaces/icurve.h"

template< class IPoint >
class Bezier : public ICurve< IPoint >
{

protected:
    /* Fields. */
    int degree;

public:
    /* Constructors. */
    Bezier( );
    /* Destructors. */
    ~Bezier( );

    /* Methods. */
    virtual void AddPoint( const IPoint& point, double weight );
    virtual void AddPoint( const IPoint& point, int point_index, double weight );

    virtual void RemovePoint( );
    virtual void RemovePoint( int point_index );

    virtual IPoint Eval( double t ) const;
    virtual vector< IPoint > ComputePoints( int n ) const;

    void ReduceDegree1(); // Metoda "odwrotna" do podnoszenia stopnia.

};

/* Constructor. */
template< class IPoint >
Bezier< IPoint >::Bezier( )
{
    degree = -1;
}

/* Destructor. */
template< class IPoint >
Bezier< IPoint >::~Bezier( )
{
}

/* Methods. */
template< class IPoint >
void Bezier< IPoint >::AddPoint( const IPoint& point, double weight = 1.0 )
{
    ICurve< IPoint >::AddPoint( point );
    degree++;
}

template< class IPoint >
void Bezier< IPoint >::AddPoint( const IPoint& point, int point_index, double weight = 1.0 )
{
    ICurve< IPoint >::AddPoint( point, point_index );
    degree++;
}

template< class IPoint >
void Bezier< IPoint >::RemovePoint( )
{
    ICurve< IPoint >::RemovePoint( );
    degree--;
}

template< class IPoint >
void Bezier< IPoint >::RemovePoint( int point_index )
{
    ICurve< IPoint >::RemovePoint( point_index );
    degree--;
}

template< class IPoint >
IPoint Bezier< IPoint >::Eval( double t ) const
{
    assert( 0.0 <= t && t <= 1.0 );

    IPoint P[degree + 1];

    for ( int i = 0; i <= degree; i++ )
        P[i] = this -> control_points[i];

    for ( int k = 1; k <= degree; k++ )
        for ( int i = 0; i <= degree - k; i++ )
            P[i] = ( 1.0 - t ) * P[i] + t * P[i + 1];

    return P[0];
}

template< class IPoint >
vector< IPoint > Bezier< IPoint >::ComputePoints( int n ) const
{
    vector< IPoint > points( n );

    for ( int i = 0; i < n; i++ )
        points[i] = Eval( static_cast<double>( i ) / ( n - 1 ) );

    return points;
}

template< class IPoint >
void Bezier< IPoint >::ReduceDegree1( )
{
    if ( degree < 2 )
        return;

    vector< IPoint > new_points( degree );
    double n1, n2, d;
    IPoint p1, p2;

    new_points[0] = this -> control_points[0];

    for ( int i = 1; i < degree / 2; i++ )
        new_points[i] = ( 1 + i / (degree - i) ) * this -> control_points[i] -
                        i /  (degree - i) * new_points[i-1];

    new_points[degree - 1] = this -> control_points[degree];

    if ( degree > 1 )
    {
        for ( int i = degree - 2; i > degree / 2; i-- )
            new_points[i] = degree / i * this -> control_points[i+1] +
                            ( 1 - degree / i ) * new_points[i + 1];
    }

    d  = degree / 2;
    n1 = d / degree - d;
    n2 = degree / d;
    p1 = ( 1 + n1 ) * this -> control_points[d] - n1 * new_points[d-1];
    p2 = n2 * this -> control_points[d+1] + ( 1 - n2 ) * new_points[d + 1];

    if ( p1 == p2 )
        new_points[d] = p1;
    else
        new_points[d] = 0.5 * p1 + 0.5 * p2;

//    copy( new_points.begin( ), new_points.end( ),
//          this -> control_points.begin( ) );

    this -> control_points.pop_back();
    this -> control_points = new_points;
    degree--;
}

#endif // BEZIER_H
